use crate::document::DocumentManager;
use codegraph_core::{GraphSystem, Result as CoreResult};
use tower_lsp::lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Range, Url};
use std::sync::Arc;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use tokio::sync::{RwLock, Mutex};
use tracing::{debug, warn};
use regex::Regex;

/// Cache entry for definition locations
#[derive(Debug, Clone)]
struct DefinitionCacheEntry {
    locations: Vec<Location>,
    timestamp: Instant,
    content_hash: u64,
}

/// Definition cache with TTL
struct DefinitionCache {
    entries: HashMap<String, DefinitionCacheEntry>,
    ttl: Duration,
}

impl DefinitionCache {
    fn new(ttl: Duration) -> Self {
        Self {
            entries: HashMap::new(),
            ttl,
        }
    }

    fn get(&self, key: &str, content_hash: u64) -> Option<Vec<Location>> {
        if let Some(entry) = self.entries.get(key) {
            if entry.timestamp.elapsed() < self.ttl && entry.content_hash == content_hash {
                return Some(entry.locations.clone());
            }
        }
        None
    }

    fn insert(&mut self, key: String, locations: Vec<Location>, content_hash: u64) {
        let entry = DefinitionCacheEntry {
            locations,
            timestamp: Instant::now(),
            content_hash,
        };
        self.entries.insert(key, entry);
    }

    fn clear(&mut self) {
        self.entries.clear();
    }
}

/// Enhanced definition provider with cross-file search and intelligent caching
pub struct DefinitionProvider {
    document_manager: Arc<DocumentManager>,
    graph_system: Arc<RwLock<Option<GraphSystem>>>,
    cache: Arc<Mutex<DefinitionCache>>,
    // Pre-compiled regex patterns for better performance
    import_patterns: HashMap<String, Regex>,
    definition_patterns: HashMap<String, Regex>,
}

impl DefinitionProvider {
    pub fn new(
        document_manager: Arc<DocumentManager>,
        graph_system: Arc<RwLock<Option<GraphSystem>>>,
    ) -> Self {
        let cache = Arc::new(Mutex::new(DefinitionCache::new(Duration::from_secs(120)))); // 2 minute TTL

        // Pre-compile import patterns for different languages
        let mut import_patterns = HashMap::new();
        let mut definition_patterns = HashMap::new();

        // Python patterns
        import_patterns.insert(
            "python".to_string(),
            Regex::new(r"(?:from\s+(\S+)\s+import\s+([^#\n]+)|import\s+([^#\n]+))").unwrap(),
        );
        definition_patterns.insert(
            "python".to_string(),
            Regex::new(r"(?:def\s+(\w+)|class\s+(\w+)|(\w+)\s*=)").unwrap(),
        );

        // JavaScript/TypeScript patterns
        import_patterns.insert(
            "javascript".to_string(),
            Regex::new(r#"(?:import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]|const\s+(\w+)\s+=\s+require\(['"]([^'"]+)['"]\))"#).unwrap(),
        );
        import_patterns.insert(
            "typescript".to_string(),
            Regex::new(r#"(?:import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]|const\s+(\w+)\s+=\s+require\(['"]([^'"]+)['"]\))"#).unwrap(),
        );

        // Rust patterns
        import_patterns.insert(
            "rust".to_string(),
            Regex::new(r"use\s+([^;]+);").unwrap(),
        );
        definition_patterns.insert(
            "rust".to_string(),
            Regex::new(r"(?:fn\s+(\w+)|struct\s+(\w+)|enum\s+(\w+)|let\s+(\w+))").unwrap(),
        );

        Self {
            document_manager,
            graph_system,
            cache,
            import_patterns,
            definition_patterns,
        }
    }

    pub async fn provide_definition(
        &self,
        params: &GotoDefinitionParams,
    ) -> CoreResult<Option<GotoDefinitionResponse>> {
        let start_time = Instant::now();
        let uri = &params.text_document_position_params.text_document.uri;
        let position = params.text_document_position_params.position;

        debug!("Providing definition for {} at {:?}", uri, position);

        // Get document
        let document = match self.document_manager.get_document(uri) {
            Some(doc) => doc,
            None => {
                warn!("Document not found for definition: {}", uri);
                return Ok(None);
            }
        };

        let (language, word, content, content_hash) = {
            let doc = document.read().await;
            let language = match &doc.language {
                Some(lang) => lang.clone(),
                None => {
                    debug!("No language detected for definition");
                    return Ok(None);
                }
            };

            // Get the word at the cursor position
            let word = match doc.get_word_at_position(position) {
                Ok(Some(word)) => word,
                Ok(None) => return Ok(None),
                Err(e) => {
                    warn!("Failed to get word at position: {}", e);
                    return Ok(None);
                }
            };

            let content = doc.content.clone();
            let content_hash = self.compute_content_hash(&content);
            (language, word, content, content_hash)
        };

        debug!("Definition word: '{}'", word);

        // Check cache first
        let cache_key = format!("{}:{}:{}:{}", uri, position.line, position.character, word);
        {
            let cache = self.cache.lock().await;
            if let Some(cached_locations) = cache.get(&cache_key, content_hash) {
                debug!(
                    "Cache hit for definition '{}' ({}ms)",
                    word,
                    start_time.elapsed().as_millis()
                );
                return self.format_definition_response(cached_locations);
            }
        }

        // Find definition locations
        let locations = self.find_definition_locations(&word, &language, &content, uri).await?;

        // Cache the results
        {
            let mut cache = self.cache.lock().await;
            cache.insert(cache_key, locations.clone(), content_hash);
        }

        let elapsed = start_time.elapsed();
        debug!(
            "Definition provided for '{}' in {}ms ({} locations)",
            word,
            elapsed.as_millis(),
            locations.len()
        );

        self.format_definition_response(locations)
    }

    /// Compute a simple hash for content to use in caching
    fn compute_content_hash(&self, content: &str) -> u64 {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        hasher.finish()
    }

    /// Format the definition response based on the number of locations
    fn format_definition_response(&self, locations: Vec<Location>) -> CoreResult<Option<GotoDefinitionResponse>> {
        if locations.is_empty() {
            Ok(None)
        } else if locations.len() == 1 {
            Ok(Some(GotoDefinitionResponse::Scalar(locations.into_iter().next().unwrap())))
        } else {
            Ok(Some(GotoDefinitionResponse::Array(locations)))
        }
    }

    async fn find_definition_locations(
        &self,
        word: &str,
        language: &str,
        content: &str,
        current_uri: &Url,
    ) -> CoreResult<Vec<Location>> {
        let mut locations = Vec::new();

        // Search in current document first using enhanced patterns
        if let Some(location) = self.find_definition_with_regex(word, language, content, current_uri) {
            locations.push(location);
        }

        // Search in other open documents
        let other_locations = self.find_definition_in_other_documents(word, language, current_uri).await?;
        locations.extend(other_locations);

        // Search for imported symbols
        let import_locations = self.find_definition_in_imports(word, language, content, current_uri).await?;
        locations.extend(import_locations);

        Ok(locations)
    }

    fn find_definition_in_content(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> Option<Location> {
        let lines: Vec<&str> = content.lines().collect();

        for (line_num, line) in lines.iter().enumerate() {
            // Look for function definitions
            if self.is_function_definition(line, word, language) {
                if let Some(range) = self.get_definition_range(line, word, line_num) {
                    return Some(Location::new(uri.clone(), range));
                }
            }

            // Look for class definitions
            if self.is_class_definition(line, word, language) {
                if let Some(range) = self.get_definition_range(line, word, line_num) {
                    return Some(Location::new(uri.clone(), range));
                }
            }

            // Look for variable definitions
            if self.is_variable_definition(line, word, language) {
                if let Some(range) = self.get_definition_range(line, word, line_num) {
                    return Some(Location::new(uri.clone(), range));
                }
            }
        }

        None
    }

    fn is_function_definition(&self, line: &str, word: &str, language: &str) -> bool {
        let trimmed = line.trim();
        match language {
            "python" => {
                trimmed.starts_with("def ") && trimmed.contains(&format!("{}(", word))
            }
            "javascript" | "typescript" => {
                (trimmed.starts_with("function ") && trimmed.contains(&format!("{}(", word)))
                    || (trimmed.contains("const ") && trimmed.contains(&format!("{} =", word)) && trimmed.contains("=>"))
                    || (trimmed.contains("let ") && trimmed.contains(&format!("{} =", word)) && trimmed.contains("=>"))
            }
            "rust" => {
                trimmed.starts_with("fn ") && trimmed.contains(&format!("{}(", word))
            }
            _ => false,
        }
    }

    fn is_class_definition(&self, line: &str, word: &str, language: &str) -> bool {
        let trimmed = line.trim();
        match language {
            "python" => {
                trimmed.starts_with("class ") && (
                    trimmed.contains(&format!("{} ", word)) ||
                    trimmed.contains(&format!("{}(", word)) ||
                    trimmed.contains(&format!("{}:", word))
                )
            }
            "javascript" | "typescript" => {
                trimmed.starts_with("class ") && trimmed.contains(&format!("{} ", word))
            }
            "rust" => {
                (trimmed.starts_with("struct ") || trimmed.starts_with("enum ")) &&
                trimmed.contains(&format!("{} ", word))
            }
            _ => false,
        }
    }

    fn is_variable_definition(&self, line: &str, word: &str, language: &str) -> bool {
        let trimmed = line.trim();
        
        // Skip comments
        if trimmed.starts_with('#') || trimmed.starts_with("//") {
            return false;
        }

        match language {
            "python" => {
                trimmed.contains(&format!("{} =", word)) && !trimmed.contains("==")
            }
            "javascript" | "typescript" => {
                (trimmed.starts_with("const ") || trimmed.starts_with("let ") || trimmed.starts_with("var ")) &&
                trimmed.contains(&format!("{} =", word))
            }
            "rust" => {
                trimmed.starts_with("let ") && trimmed.contains(&format!("{} =", word))
            }
            _ => {
                trimmed.contains(&format!("{} =", word)) && !trimmed.contains("==")
            }
        }
    }

    fn get_definition_range(&self, line: &str, word: &str, line_num: usize) -> Option<Range> {
        if let Some(start_col) = line.find(word) {
            let start = Position::new(line_num as u32, start_col as u32);
            let end = Position::new(line_num as u32, (start_col + word.len()) as u32);
            Some(Range::new(start, end))
        } else {
            None
        }
    }

    /// Enhanced definition finding using regex patterns
    fn find_definition_with_regex(
        &self,
        word: &str,
        language: &str,
        content: &str,
        uri: &Url,
    ) -> Option<Location> {
        if let Some(pattern) = self.definition_patterns.get(language) {
            for (line_num, line) in content.lines().enumerate() {
                if let Some(captures) = pattern.captures(line) {
                    // Check each capture group for the word
                    for i in 1..captures.len() {
                        if let Some(captured) = captures.get(i) {
                            if captured.as_str() == word {
                                if let Some(range) = self.get_definition_range(line, word, line_num) {
                                    return Some(Location::new(uri.clone(), range));
                                }
                            }
                        }
                    }
                }
            }
        }

        // Fallback to the original method
        self.find_definition_in_content(word, language, content, uri)
    }

    /// Find definitions in other open documents
    async fn find_definition_in_other_documents(
        &self,
        word: &str,
        language: &str,
        current_uri: &Url,
    ) -> CoreResult<Vec<Location>> {
        let mut locations = Vec::new();

        // Get all open documents
        let document_uris = self.document_manager.list_documents();
        let documents: Vec<_> = document_uris.iter()
            .filter_map(|uri| self.document_manager.get_document(uri).map(|doc| (uri.clone(), doc)))
            .collect();

        for (uri, document) in documents {
            // Skip the current document
            if uri == *current_uri {
                continue;
            }

            let doc = document.read().await;

            // Only search in documents of the same language or related languages
            if let Some(doc_language) = &doc.language {
                if self.is_related_language(language, doc_language) {
                    if let Some(location) = self.find_definition_with_regex(word, doc_language, &doc.content, &uri) {
                        locations.push(location);
                    }
                }
            }
        }

        Ok(locations)
    }

    /// Find definitions in imported modules
    async fn find_definition_in_imports(
        &self,
        word: &str,
        language: &str,
        content: &str,
        current_uri: &Url,
    ) -> CoreResult<Vec<Location>> {
        let mut locations = Vec::new();

        if let Some(import_pattern) = self.import_patterns.get(language) {
            for line in content.lines() {
                if let Some(captures) = import_pattern.captures(line) {
                    // Extract import information and try to resolve the imported file
                    if let Some(import_info) = self.extract_import_info(&captures, language) {
                        if import_info.symbols.contains(&word.to_string()) {
                            // Try to resolve the import path to a file
                            if let Some(import_uri) = self.resolve_import_path(&import_info.module_path, current_uri, language) {
                                // Search for the definition in the imported file
                                if let Some(document) = self.document_manager.get_document(&import_uri) {
                                    let doc = document.read().await;
                                    if let Some(location) = self.find_definition_with_regex(word, language, &doc.content, &import_uri) {
                                        locations.push(location);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        Ok(locations)
    }

    /// Check if two languages are related (e.g., TypeScript and JavaScript)
    fn is_related_language(&self, lang1: &str, lang2: &str) -> bool {
        if lang1 == lang2 {
            return true;
        }

        match (lang1, lang2) {
            ("javascript", "typescript") | ("typescript", "javascript") => true,
            ("python", "python3") | ("python3", "python") => true,
            _ => false,
        }
    }

    /// Extract import information from regex captures
    fn extract_import_info(&self, captures: &regex::Captures, language: &str) -> Option<ImportInfo> {
        match language {
            "python" => {
                if let Some(module) = captures.get(1) {
                    // from module import symbols
                    if let Some(symbols_str) = captures.get(2) {
                        let symbols: Vec<String> = symbols_str.as_str()
                            .split(',')
                            .map(|s| s.trim().to_string())
                            .collect();
                        return Some(ImportInfo {
                            module_path: module.as_str().to_string(),
                            symbols,
                        });
                    }
                } else if let Some(module) = captures.get(3) {
                    // import module
                    return Some(ImportInfo {
                        module_path: module.as_str().to_string(),
                        symbols: vec![module.as_str().to_string()],
                    });
                }
            }
            "javascript" | "typescript" => {
                if let Some(symbols_str) = captures.get(1) {
                    // import { symbols } from "module"
                    let symbols: Vec<String> = symbols_str.as_str()
                        .split(',')
                        .map(|s| s.trim().to_string())
                        .collect();
                    if let Some(module) = captures.get(3) {
                        return Some(ImportInfo {
                            module_path: module.as_str().to_string(),
                            symbols,
                        });
                    }
                } else if let Some(default_import) = captures.get(2) {
                    // import defaultExport from "module"
                    if let Some(module) = captures.get(3) {
                        return Some(ImportInfo {
                            module_path: module.as_str().to_string(),
                            symbols: vec![default_import.as_str().to_string()],
                        });
                    }
                }
            }
            _ => {}
        }

        None
    }

    /// Resolve import path to actual file URI
    fn resolve_import_path(&self, import_path: &str, current_uri: &Url, language: &str) -> Option<Url> {
        // This is a simplified implementation
        // In practice, this would need to handle:
        // - Relative imports (./module, ../module)
        // - Absolute imports from node_modules, site-packages, etc.
        // - Language-specific module resolution rules

        if import_path.starts_with('.') {
            // Relative import
            if let Ok(current_path) = current_uri.to_file_path() {
                if let Some(parent) = current_path.parent() {
                    let resolved_path = parent.join(import_path);

                    // Try different file extensions
                    let extensions = match language {
                        "python" => vec!["py", "pyi"],
                        "javascript" => vec!["js", "jsx"],
                        "typescript" => vec!["ts", "tsx", "d.ts"],
                        _ => vec![],
                    };

                    for ext in extensions {
                        let file_path = resolved_path.with_extension(ext);
                        if file_path.exists() {
                            if let Ok(uri) = Url::from_file_path(file_path) {
                                return Some(uri);
                            }
                        }
                    }
                }
            }
        }

        None
    }
}

/// Import information extracted from import statements
#[derive(Debug, Clone)]
struct ImportInfo {
    module_path: String,
    symbols: Vec<String>,
}
